package com.yochi.pictureseditor.libraries.cropimage;

import java.util.concurrent.CountDownLatch;

import com.yochi.pictureseditor.Processor;
import com.yochi.pictureseditor.R;
import com.yochi.pictureseditor.factories.ImageFactory;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.media.FaceDetector;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class CropImage extends MonitoredActivity {

	private static final String TAG                    = "CropImage";
	
	public static final  String IMAGE_PATH             = "image-path";
	public static final  String IMAGE				   = "image";
	public static final  String SCALE                  = "scale";
	public static final  String ORIENTATION_IN_DEGREES = "orientation_in_degrees";
	public static final  String ASPECT_X               = "aspectX";
	public static final  String ASPECT_Y               = "aspectY";
	public static final  String OUTPUT_X               = "outputX";
	public static final  String OUTPUT_Y               = "outputY";
	public static final  String SCALE_UP_IF_NEEDED     = "scaleUpIfNeeded";
	public static final  String CIRCLE_CROP            = "circleCrop";
	public static final  String RETURN_DATA            = "return-data";
	public static final  String RETURN_DATA_AS_BITMAP  = "data";
	public static final  String ACTION_INLINE_DATA     = "inline-data";
	
	// Views
	private Button bCancel;
	private ImageView ivRotateLeft;
	private ImageView ivRotateRight;
	private Button bValidate;

	// These are various options can be specified in the intent.
	private       boolean               mDoFaceDetection = true;
	private       boolean               mCircleCrop      = false;
	private final Handler               mHandler         = new Handler();
	
	private final BitmapManager.ThreadSet mDecodingThreads = new BitmapManager.ThreadSet();

	private int             mAspectX;
	private int             mAspectY;
	private int             mOutputX;
	private int             mOutputY;
	private boolean         mScale;
	private CropImageView   mImageView;
	private Bitmap          mBitmap;

	protected boolean       mWaitingToPick; 	// Whether we are wait the user to pick a face.
	protected boolean       mSaving;  		// Whether the "save" button is already clicked.
	protected HighlightView mCrop;

	// These options specifiy the output image size and whether we should
	// scale the output to fit it (or just crop it).
	private boolean mScaleUp = true;

	
	  ////////////////////////
	 // ACTIVITY LIFECYCLE //
	////////////////////////
	
	@Override
	public void onCreate(Bundle icicle) {
		Log.i(TAG, "CropImage.onCreate");
		super.onCreate(icicle);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_cropimage);

		mImageView = (CropImageView) findViewById(R.id.image);

		// Get intent parameters
		Intent intent = getIntent();
		Bundle extras = intent.getExtras();
		if (extras != null) {

			// Circle crop case
			if (extras.getBoolean(CIRCLE_CROP) != false) {
//				if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { //TESTME available from API11 only
//					mImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//				}
				mCircleCrop = true;
				mAspectX = 1;
				mAspectY = 1;
			}

			// Get image
			mBitmap = ImageFactory.imageToBitmap(Processor.currentImage);
			Log.i(TAG, "CropImage.onCreate.mBitmap="+mBitmap);
			
			// Get crop aspect
			if (extras.containsKey(ASPECT_X) && extras.get(ASPECT_X) instanceof Integer) {
				mAspectX = extras.getInt(ASPECT_X);
			} else {
				throw new IllegalArgumentException("aspect_x must be integer");
			}
			if (extras.containsKey(ASPECT_Y) && extras.get(ASPECT_Y) instanceof Integer) {
				mAspectY = extras.getInt(ASPECT_Y);
			} else {
				throw new IllegalArgumentException("aspect_y must be integer");
			}
			
			// Get output aspect
			mOutputX = extras.getInt(OUTPUT_X);
			mOutputY = extras.getInt(OUTPUT_Y);
			mScale = extras.getBoolean(SCALE, true);
			mScaleUp = extras.getBoolean(SCALE_UP_IF_NEEDED, true);
		}

		if (mBitmap == null) {
			Log.w(TAG, "null bitmap - finish activity");
			finish();
			return;
		}

		// Make UI fullscreen.
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		
		// Fetch views
		this.bCancel = (Button) findViewById(R.id.discard);
		this.ivRotateLeft = (ImageView) findViewById(R.id.rotateLeft);
		this.ivRotateRight = (ImageView) findViewById(R.id.rotateRight);
		this.bValidate = (Button) findViewById(R.id.save);
		
		// Set UI button listeners
		this.bCancel.setOnClickListener(
				new View.OnClickListener() {
					public void onClick(View v) {
						setResult(RESULT_CANCELED);
						finish();
					}
				});
		
		this.bValidate.setOnClickListener(
				new View.OnClickListener() {
					public void onClick(View v) {
						try {
							onSaveClicked();
						} catch (Exception e) {
							finish();
						}
					}
				});
		
		this.ivRotateLeft.setOnClickListener(
				new View.OnClickListener() {
					public void onClick(View v) {
						mBitmap = Util.rotateImage(mBitmap, -90);
						RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
						mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
						mRunFaceDetection.run();
					}
				});
		
		this.ivRotateRight.setOnClickListener(
				new View.OnClickListener() {
					public void onClick(View v) {
						mBitmap = Util.rotateImage(mBitmap, 90);
						RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
						mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
						mRunFaceDetection.run();
					}
				});
		
		// Start face detection
		startFaceDetection();
		
	} /* onCreate */

	@Override
	protected void onPause() {
		super.onPause();
		BitmapManager.instance().cancelThreadDecoding(mDecodingThreads);
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (mBitmap != null) {
			mBitmap.recycle();
		}
	}
	
	
	  ////////////////////
	 // FACE DETECTION //
	////////////////////

	private void startFaceDetection() {
		if (isFinishing()) {
			return;
		}
		mImageView.setImageBitmapResetBase(mBitmap, true);

		Util.startBackgroundJob(this, null, "Please wait\u2026", new Runnable() {
			public void run() {
				final CountDownLatch latch = new CountDownLatch(1);
				final Bitmap b = mBitmap;
				mHandler.post(new Runnable() {
					public void run() {
						if (b != mBitmap && b != null) {
							mImageView.setImageBitmapResetBase(b, true);
							mBitmap.recycle();
							mBitmap = b;
						}
						if (mImageView.getScale() == 1F) {
							mImageView.center(true, true);
						}
						latch.countDown();
					}
				});
				try {
					latch.await();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				mRunFaceDetection.run();
			}
		}, mHandler);
	}


	private void onSaveClicked() throws Exception {
		if (mSaving == true || mCrop == null) {
			return;
		}
		mSaving = true;

		Rect r = mCrop.getCropRect();
		int width = r.width();
		int height = r.height();

		// If we are circle cropping, we want alpha channel, which is the third param here.
		Bitmap croppedImage;
		try {
			croppedImage = Bitmap.createBitmap(width, height, mCircleCrop ? Bitmap.Config.ARGB_8888 : Bitmap.Config.ARGB_8888);
		} catch (Exception e) {
			throw e;
		}
		
		if (croppedImage == null) {
			return;
		}

		{
			Canvas canvas = new Canvas(croppedImage);
			Rect dstRect = new Rect(0, 0, width, height);
			canvas.drawBitmap(mBitmap, r, dstRect, null);
		}
		
		// If image should be crop by a circle
		if (mCircleCrop) {
			Canvas c = new Canvas(croppedImage);
			Path p = new Path();
			p.addCircle(width / 2F, height / 2F, width / 2F, Path.Direction.CW);
			c.clipPath(p, Region.Op.DIFFERENCE);
			c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
		}

		// If the output is required to a specific size then scale or fill 
		if (mOutputX != 0 && mOutputY != 0) {
			if (mScale) {
				Bitmap old = croppedImage;
				croppedImage = Util.transform(new Matrix(), croppedImage, mOutputX, mOutputY, mScaleUp);
				if (old != croppedImage) {
					old.recycle();
				}
			} else {
				Bitmap b = Bitmap.createBitmap(mOutputX, mOutputY, Bitmap.Config.RGB_565);
				Canvas canvas = new Canvas(b);
				Rect srcRect = mCrop.getCropRect();
				Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);
				int dx = (srcRect.width() - dstRect.width()) / 2;
				int dy = (srcRect.height() - dstRect.height()) / 2;
				srcRect.inset(Math.max(0, dx), Math.max(0, dy)); 	// If the srcRect is too big, use the center part of it. 
				dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));  // If the dstRect is too big, use the center part of it.
				canvas.drawBitmap(mBitmap, srcRect, dstRect, null); // Draw the cropped bitmap in the center
				croppedImage.recycle();								
				croppedImage = b;									// Set the cropped bitmap as the new bitmap
			}
		}
		
		if(croppedImage != null) {
			Processor.currentImage = ImageFactory.bitmapToImage(croppedImage);
			setResult(RESULT_OK);
			Log.i(TAG, "finish CropImage activity with success");
			finish();
			
		} else {
			setResult(RESULT_CANCELED);
			Log.i(TAG, "finish CropImage activity with error - croppedImage=null");
			finish();
			return;
			
		}
	}


	protected Runnable mRunFaceDetection = new Runnable() {
		float mScale = 1F;
		Matrix mImageMatrix;
		FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
		int mNumFaces;

		// For each face, we create a HightlightView for it.
		private void handleFace(FaceDetector.Face f) {

			PointF midPoint = new PointF();

			int r = ((int) (f.eyesDistance() * mScale)) * 2;
			f.getMidPoint(midPoint);
			midPoint.x *= mScale;
			midPoint.y *= mScale;

			int midX = (int) midPoint.x;
			int midY = (int) midPoint.y;

			HighlightView hv = new HighlightView(mImageView);

			int width = mBitmap.getWidth();
			int height = mBitmap.getHeight();

			Rect imageRect = new Rect(0, 0, width, height);

			RectF faceRect = new RectF(midX, midY, midX, midY);
			faceRect.inset(-r, -r);
			if (faceRect.left < 0) {
				faceRect.inset(-faceRect.left, -faceRect.left);
			}

			if (faceRect.top < 0) {
				faceRect.inset(-faceRect.top, -faceRect.top);
			}

			if (faceRect.right > imageRect.right) {
				faceRect.inset(faceRect.right - imageRect.right,
						faceRect.right - imageRect.right);
			}

			if (faceRect.bottom > imageRect.bottom) {
				faceRect.inset(faceRect.bottom - imageRect.bottom,
						faceRect.bottom - imageRect.bottom);
			}

			hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop, mAspectX != 0 && mAspectY != 0);

			mImageView.add(hv);
		}

		// Create a default HightlightView if we found no face in the picture.
		private void makeDefault() {

			HighlightView hv = new HighlightView(mImageView);

			int width = mBitmap.getWidth();
			int height = mBitmap.getHeight();

			Rect imageRect = new Rect(0, 0, width, height);

			// make the default size about 4/5 of the width or height
			int cropWidth = Math.min(width, height) * 4 / 5;
			int cropHeight = cropWidth;

			if (mAspectX != 0 && mAspectY != 0) {

				if (mAspectX > mAspectY) {
					cropHeight = cropWidth * mAspectY / mAspectX;
				} else {
					cropWidth = cropHeight * mAspectX / mAspectY;
				}
			}

			int x = (width - cropWidth) / 2;
			int y = (height - cropHeight) / 2;

			RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
			hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
					mAspectX != 0 && mAspectY != 0);

			mImageView.mHighlightViews.clear(); // Thong added for rotate

			mImageView.add(hv);
		}

		// Scale the image down for faster face detection.
		private Bitmap prepareBitmap() {

			if (mBitmap == null) {
				return null;
			}

			// 256 pixels wide is enough.
			if (mBitmap.getWidth() > 256) {
				mScale = 256.0F / mBitmap.getWidth();
			}
			Matrix matrix = new Matrix();
			matrix.setScale(mScale, mScale);
			return Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
		}

		public void run() {

			mImageMatrix = mImageView.getImageMatrix();
			Bitmap faceBitmap = prepareBitmap();

			mScale = 1.0F / mScale;
			if (faceBitmap != null && mDoFaceDetection) {
				FaceDetector detector = new FaceDetector(faceBitmap.getWidth(), faceBitmap.getHeight(), mFaces.length);
				mNumFaces = detector.findFaces(faceBitmap, mFaces);
			}

			if (faceBitmap != null && faceBitmap != mBitmap) {
				faceBitmap.recycle();
			}

			mHandler.post(new Runnable() {
				public void run() {

					mWaitingToPick = mNumFaces > 1;
					if (mNumFaces > 0) {
						for (int i = 0; i < mNumFaces; i++) {
							handleFace(mFaces[i]);
						}
					} else {
						makeDefault();
					}
					mImageView.invalidate();
					if (mImageView.mHighlightViews.size() == 1) {
						mCrop = mImageView.mHighlightViews.get(0);
						mCrop.setFocus(true);
					}

					if (mNumFaces > 1) {
						Toast.makeText(CropImage.this,
								"Multi face crop help",
								Toast.LENGTH_SHORT).show();
					}
				}
			});
		}
	};

}
